﻿using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using NetduinoHappyHour.Util;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace NetduinoHappyHour.Hardware
{
    /// <summary>
    /// class that allows to run one wheel, which one needs to be defined by complete implementation
    /// Written by: Salvador Richter
    /// Appache License Version 2.0 
    /// </summary>
    public abstract class DriveCommandSingle : IDriveCommand
    {
        private int _halfStepFirst = 0;
        private int _halfStepSecond = 0;

        /// <summary>
        /// The wait duration to hold a step, splitted into first and second half
        /// </summary>
        public int StepDuration { get { return _halfStepFirst + _halfStepSecond; } set { _halfStepFirst = value / 2; _halfStepSecond = _halfStepFirst + (value % 2); } }

        /// <summary>
        /// The _serialPort or pin for the stepper's clock
        /// </summary>
        public abstract Cpu.Pin PortPinStepper { get; }

        /// <summary>
        /// The _serialPort to determine the stepping direction
        /// </summary>
        public abstract Cpu.Pin PortPinDirection { get; }

        /// <summary>
        /// Number of steps to run for fixed commands
        /// </summary>
        public int StepsToRun { get; private set; }

        /// <summary>
        /// number of steps already ran in this command
        /// </summary>
        public int StepsAlreadyRan { get; private set; }

        /// <summary>
        /// true = forward, false = backward
        /// </summary>
        public bool IsDirectionForward { get; set; }

        /// <summary>
        /// Interrupts execution of DriveCommand.
        /// DriveCommand is executed as long as is valid returns true
        /// </summary>
        public bool IsValid { get; set; }

        /// <summary>
        /// Interrupts execution of DriveCommand.
        /// DriveCommand is executed as long as is valid returns true
        /// </summary>
        public bool IsPaused { get; set; }

        /// <summary>
        /// DriveComand is endless executed if set to true
        /// Default: false
        /// </summary>
        public bool IsEndlessRunning { get; set; }

        /// <summary>
        /// private default construction to do basic init
        /// </summary>
        private DriveCommandSingle()
        {
            IsDirectionForward = true;
            IsEndlessRunning = true;
            IsValid = true;
            IsPaused = false;
            StepsAlreadyRan = 0;
            // initialize to enable zero avoiding
            StepDuration = SystemConfig.Instance().DelayStepper;
        }

        /// <summary>
        /// Creates an endless running DriveCommand, 
        /// it will execute until IsValid is set to false
        /// </summary>
        ///
        /// <param name="StepDurationToBeSet">step duration of stepper engine</param>
        public DriveCommandSingle(int StepDurationToBeSet)
            : this()
        {
            // avoid zero Value
            if (StepDurationToBeSet != 0)
            {
                StepDuration = System.Math.Abs(StepDurationToBeSet);
            }
        }

        /// <summary>Creates a DriveCommand with fix number of steps and given step duration</summary>
        ///
        /// <param name="StepsL">number of steps to be run by stepper engine, for movement per step see SystemConfig, on negative Value IsDirectionForward is set to false</param>
        /// <param name="StepDurationToBeSet">step duration of stepper engine</param>
        public DriveCommandSingle(int Steps, int StepDurationToBeSet)
            : this()
        {
            IsEndlessRunning = false;
            IsDirectionForward = (Steps >= 0);
            StepsToRun = System.Math.Abs(Steps);

            // avoid zero Value
            if (StepDurationToBeSet != 0)
            {
                StepDuration = System.Math.Abs(StepDurationToBeSet);
            }
        }

        /// <summary>
        /// Executes a DriveCommand. Each step if postponed by a wait cycle, so no special handling for engines rotation required
        /// Uses Cpu.Pin PortPinStepper and Cpu.Pin PortPinDirection for stepper control
        /// </summary>
        public void Execute()
        {
            StepperSleep reactivateSleepMode = null;
            // clock
            OutputPort stepper = new OutputPort(PortPinStepper, false);
            // direction, invert IsDirectionForward because IO _serialPort is low active
            OutputPort direction = new OutputPort(PortPinDirection, !IsDirectionForward);

            // activate stepper if required, store sleep Mode
            if (StepperSleep.IsSleepMode)
            {
                reactivateSleepMode = new StepperSleep(false);
                reactivateSleepMode.Execute();
            }
            while (IsValid && NextStepAllowed())
            {
                if (IsPaused)
                {
                    // just sleep
                    System.Threading.Thread.Sleep(StepDuration);
                }
                else
                {
                    //TODO add set direction if required
                    direction.Write(!IsDirectionForward);
                    // toggle the Value to create a clock, driver reacts on rising edge only
                    System.Threading.Thread.Sleep(_halfStepFirst);
                    stepper.Write(false);
                    System.Threading.Thread.Sleep(_halfStepSecond);
                    stepper.Write(true);
                    StepsAlreadyRan++;
                }
            }

            // deactivate steppers if they slept before
            if (reactivateSleepMode != null)
            {
                StepperSleep.IsSleepMode = true;
                reactivateSleepMode.Execute();
            }

            stepper.Dispose();
            direction.Dispose();
        }

        /// <summary>
        /// Determines weather execution of next step is allowed or not
        /// </summary>
        /// <returns>true if next step is allowed to execute</returns>
        public bool NextStepAllowed()
        {
            return IsEndlessRunning || StepsToRun > StepsAlreadyRan;
        }

    }
}
